import * as path from 'path';
import * as zlib from 'zlib';
import * as fs from 'fs';

type ZipConfig = { include: string[], exclude: string[], files: string[], out: string, in?: { name: string, content: any }[] };
type ZipGroupConfig = {
    zipConfig: ZipConfig[];
    groupConfig: string | (string[]);
};
type RESGroupConfig = { name: string, keys: string };

const configURL = "./resource/zip_load_config.json";
const zipGroupConfig: ZipGroupConfig = fs.existsSync(configURL) ? JSON.parse(fs.readFileSync(configURL).toString()) : { groupConfig: [] };
const zipJSON: { zips: string[], files: { [key: string]: { s: number, l: number, z: string | number } } } = { zips: [], files: {} };
const groups: { [key: string]: RESGroupConfig } = {};
const depends: { [key: string]: RESGroupConfig } = {};
const dependZips: { [key: string]: RESGroupConfig } = {};

export class BakeInfo {
    currentRoot = "";
    readonly defaultRoot = "resource/";

    get root() {
        return this.currentRoot || this.defaultRoot;
    }
}

export const bakeInfo = new BakeInfo();

export const nameSelectorBake = (p: string) => {
    if (p.indexOf("2d/") > 0) {
        if (p.indexOf("sheet/") >= 0) {
            return "";
        }

        return path.basename(p).replace(/\./gi, "_");
    }

    return p.replace(bakeInfo.root, "");
};

export const nameSelectorPublish = (p: string) => {
    if (p.indexOf("2d/") > 0) {
        if (p.indexOf("UI_PNG_01/") >= 0 && p.indexOf("New_Collimation_mirror_frame2") == -1) {
            return "";
        }

        if (p.indexOf("sheet/") >= 0 && p.indexOf(".png") >= 0) {
            return "";
        }

        return path.basename(p).replace(/\./gi, "_");
    }

    return p.replace(bakeInfo.root, "");
};

export const groupSelector = (file: string) => {

    return null;
}

function updateDepend(groupConfig: RESGroupConfig, asset: string, origin?: string) {
    const assetPath = origin || path.normalize(process.cwd() + "/resource/" + asset);
    if (!fs.existsSync(assetPath)) {
        console.log(assetPath);
        return;
    }

    if (
        assetPath.indexOf(".scene.json") >= 0 ||
        assetPath.indexOf(".prefab.json") >= 0
    ) {
        const assets = JSON.parse(fs.readFileSync(assetPath).toString()).assets as string[];
        if (assets && assets.length > 0) {
            for (const asset of assets) {
                updateDepend(groupConfig, asset);
            }
        }
    }
    else if (assetPath.indexOf(".mat.json") >= 0) {
        const KHR_techniques = JSON.parse(fs.readFileSync(assetPath).toString()).materials[0].extensions.KHR_techniques_webgl;
        const uniformValues = KHR_techniques.values;

        for (const k in uniformValues) {
            const value = uniformValues[k];
            if (typeof value === "string") {
                if (value.indexOf("builtin") >= 0) {
                    continue;
                }

                updateDepend(groupConfig, value);
            }
        }

        if (KHR_techniques.technique.indexOf("builtin") < 0) {
            updateDepend(groupConfig, KHR_techniques.technique);
        }
    }
    else if (assetPath.indexOf(".shader.json") >= 0) {
        const shaders = JSON.parse(fs.readFileSync(assetPath).toString()).extensions.KHR_techniques_webgl.shaders;

        for (const shader of shaders) {
            if (shader.uri.indexOf("builtin") >= 0) {
                continue;
            }

            updateDepend(groupConfig, shader.uri);
        }

        if(asset.indexOf("builtin") < 0){            
            const dependGroupName = groupConfig.name + "_depend";
            const depend = depends[dependGroupName] = depends[dependGroupName] || { name: dependGroupName, keys: "" };
            if (depend.keys.indexOf(asset) < 0) {
                depend.keys += asset + ",";
            }
        }
    }
    else if (assetPath.indexOf(".image.json") >= 0) {
        updateDepend(groupConfig, JSON.parse(fs.readFileSync(assetPath).toString()).name);
    }
    else if (!origin) {
        const dependGroupName = groupConfig.name + "_depend";
        const depend = depends[dependGroupName] = depends[dependGroupName] || { name: dependGroupName, keys: "" };
        if (depend.keys.indexOf(asset) < 0) {
            depend.keys += asset + ",";
        }
    }
}

function updateDependZip(groupConfig: RESGroupConfig, asset: string) {
    const item = zipJSON.files[asset];
    if (!item) {
        return;
    }

    const dependGroupName = groupConfig.name + "_zip";
    const dependZip = dependZips[dependGroupName] = dependZips[dependGroupName] || { name: dependGroupName, keys: "" };
    const zip = zipJSON.zips[item.z];
    if (zip.indexOf("jsonbin") < 0 && dependZip.keys.indexOf(zip) < 0) {
        dependZip.keys += zip + ",";
    }
}

export class ZipPlugin implements plugins.Command {
    public constructor(public nameSelector: (p: string) => string | null) {
    }

    private _addFile(file: plugins.File, zipConfig: ZipConfig, content?: any) {
        const originFileName = file.origin;
        const resName = this.nameSelector(originFileName);

        if (!resName) {
            return;
        }

        let add = false;

        if (originFileName.indexOf("res.json") < 0) {
            for (const filter of zipConfig.files) {
                if (originFileName.indexOf(filter) >= 0) {
                    add = true;
                    break;
                }
            }

            if (!add) {
                for (const filter of zipConfig.include) {
                    if (originFileName.indexOf(filter) >= 0) {
                        add = true;
                        break;
                    }
                }

                if (add) {
                    for (const filter of zipConfig.exclude) {
                        if (originFileName.indexOf(filter) >= 0) {
                            add = false;
                            break;
                        }
                    }
                }
            }
        }

        if (add) {
            if (zipJSON.files[resName] && zipJSON.files[resName].z !== zipConfig.out) {
                console.log(originFileName, "已被添加到另一个资源包", zipJSON.files[resName].z as string);
                return;
            }

            if (!zipConfig.in) {
                zipConfig.in = [];
            }

            const item = { name: resName, content: content };
            zipConfig.in.push(item);
            zipJSON.files[resName] = { s: 0, l: 0, z: zipConfig.out };
        }
    }

    async onFile(file: plugins.File) {
        for (const zipConfig of zipGroupConfig.zipConfig) {
            this._addFile(file, zipConfig, file.contents);
        }

        return file;
    }

    async onFinish(commandContext: plugins.CommandContext) {
        for (const zipConfig of zipGroupConfig.zipConfig) {
            if (zipConfig.in) {
                console.log("压缩资源包", zipConfig.out);
                let totalLength = 0;

                for (const item of zipConfig.in) {
                    const itemTODO = zipJSON.files[item.name];
                    itemTODO.s = totalLength;
                    itemTODO.l = item.content.byteLength;
                    itemTODO.z = zipJSON.zips.length;
                    totalLength += item.content.byteLength;
                }

                if (zipConfig.in[0].name.indexOf(".json") >= 0 && zipConfig.in[0].name.indexOf(".jsonbin") < 0) {
                    const jsons = {};
                    zipConfig.in.forEach((item) => {
                        jsons[item.name] = JSON.parse(item.content.toString());
                    });
                    const jsonContent = JSON.stringify(jsons, null, '\t');

                    commandContext.createFile("resource/" + zipConfig.out, await zip(jsonContent), { type: "bin" });
                    // commandContext.createFile("resource/" + zipConfig.out, new Buffer(jsonContent), { type: "json" });
                }
                else {
                    const bufferList = zipConfig.in.map(item => item.content);

                    commandContext.createFile("resource/" + zipConfig.out, await zip(Buffer.concat(bufferList)), { type: "bin" });
                    // commandContext.createFile("resource/" + zipConfig.out, Buffer.concat(bufferList), { type: "bin" });
                }

                zipJSON.zips.push(zipConfig.out);
            }
        }

        {
            const indexJsonBuffer = await zip(JSON.stringify(zipJSON));
            commandContext.createFile("resource/zip.bin", indexJsonBuffer, { type: "bin" });
        }

        {
            // const indexJsonBuffer = new Buffer(JSON.stringify(zipJSON));
            // commandContext.createFile("resource/zip.json", indexJsonBuffer, { type: "json" });
        }
    }
}

export class ModifyDefaultResJSONPlugin implements plugins.Command {
    private readonly _subkeys: { [key: string]: string } = {};
    constructor(public root: string, public nameSelector: (p: string) => string | null) {
    }

    async onFile(file: plugins.File) {
        for (let i = 0, l = zipGroupConfig.groupConfig.length; i < l; i += 2) {
            const groupName = zipGroupConfig.groupConfig[i] as string;
            for (const config of zipGroupConfig.groupConfig[i + 1]) {
                if (file.origin.indexOf(config) >= 0) {
                    const resName = this.nameSelector(file.origin);
                    if (resName) {
                        const group = groups[groupName] = groups[groupName] || { name: groupName, keys: "" };
                        if (group.keys.indexOf(resName) < 0) {
                            updateDepend(group, resName, file.origin);
                            group.keys += resName + ",";
                        }
                    }
                }
            }
        }

        if (file.origin.indexOf("2d/") >= 0 && file.origin.indexOf("sheet/") >= 0 && file.origin.indexOf(".json") >= 0) {
            const frames = JSON.parse(fs.readFileSync(file.path).toString()).frames as { [key: string]: any };
            let subkeys = "";
            for (const k in frames) {
                subkeys += k + ",";
            }

            if (subkeys) {
                subkeys = subkeys.substr(0, subkeys.length - 1);
            }
            this._subkeys[file.origin.replace("resource/", "")] = subkeys;
        }

        if (file.origin.indexOf("default.res.json") >= 0) {
            let content = this.root ? file.contents.toString().replace(new RegExp(this.root, "gi"), "") : file.contents.toString();

            const json = JSON.parse(content);
            for (const k in groups) {
                if (!json.groups) {
                    json.groups = [];
                }

                const group = groups[k];

                if (group.keys) {
                    if (group.keys && group.keys.charAt(group.keys.length - 1) === ",") {
                        group.keys = group.keys.substr(0, group.keys.length - 1);
                    }

                    const assets = group.keys.split(",");
                    for (const asset of assets) {
                        updateDependZip(group, asset);
                    }
    
                    json.groups.push(group);
                }
            }

            for (const k in depends) {
                if (!json.groups) {
                    json.groups = [];
                }

                const depend = depends[k];

                if (depend.keys) {
                    if (depend.keys && depend.keys.charAt(depend.keys.length - 1) === ",") {
                        depend.keys = depend.keys.substr(0, depend.keys.length - 1);
                    }

                    const assets = depend.keys.split(",");
                    for (const asset of assets) {
                        updateDependZip(depend, asset);
                    }
    
                    json.groups.push(depend);
                }
            }

            for (const k in dependZips) {
                if (!json.groups) {
                    json.groups = [];
                }

                const dependZip = dependZips[k];
                if (dependZip.keys) {
                    if (dependZip.keys.charAt(dependZip.keys.length - 1) === ",") {
                        dependZip.keys = dependZip.keys.substr(0, dependZip.keys.length - 1);
                    }

                    json.groups.push(dependZip);
                }
            }

            for (const item of json.resources as { url: string, type: string, name: string, subkeys?: string }[]) {
                if (item.url.indexOf("2d/") >= 0 && item.url.indexOf("sheet/") >= 0) {
                    item.type = "sheet";
                    item.subkeys = this._subkeys[item.url];
                }

                if (!item.name) {
                    item.name = "remove_" + Math.random();
                }
            }

            file.contents = new Buffer(JSON.stringify(json));
        }

        return file;
    }

    async onFinish(commandContext: plugins.CommandContext) {

    }
}

export class InspectorFilterPlugin implements plugins.Command {
    constructor(public enabled: boolean = true) {
    }

    async onFile(file: plugins.File) {
        if (this.enabled && file.origin.indexOf("inspector.js") >= 0 || file.origin.indexOf("inspector.min.js") >= 0) {
            return null;
        }

        return file;
    }

    async onFinish(commandContext: plugins.CommandContext) {
    }
}

export class WriteDataJson implements plugins.Command {

    async onFile(file: plugins.File) {
        return file;
    }

    async onFinish(commandContext: plugins.CommandContext) {
        let versionPath = "resource/version.json";
        let content = fs.readFileSync(versionPath).toString();
        if (!content) {
            console.error("读取版本号失败！未找到文件=" + versionPath);
            return;
        }
        let versions = JSON.parse(content);
        let remoteSubdir = "/res" + versions["res"];
        let to = "../remote" + remoteSubdir + "/resource/data" + versions["data"] + ".json";
        fs.writeFileSync(to, fs.readFileSync("resource/data.json"));
    }
}

function zip(input: any) {
    return new Promise<Buffer>((resolve, reject) => {
        zlib.deflate(input, (err, buffer) => {
            if (!err) {
                resolve(buffer);
            }
            else {
                reject(err);
            }
        });
    });
}